package org.gzc.stack.calculate;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @Description: 中缀表达式计算(支持多位数计算, 小数点计算 , 去除空格, 不支持负数)
 * @Author: guozongchao
 * @Date: 2020/8/5 14:16
 */
public class DefaultCalculateStrategy implements CalculateStrategy {

    @Override
    public double calculate(String expression) {
        checkExpression(expression);
        List<String> expList = splitExpression(expression);   //获取切分后的表达式列表

        Stack<Double> numberStack = new Stack<>();  //操作数栈
        Stack<String> operatorStack = new Stack<>();  //操作符栈

        for (String s : expList) {
            //如果是操作数，则直接入栈
            if (isNumberOrPoint(s)) {
                numberStack.push(Double.valueOf(s));
            } else {   //如果是操作符
                if (operatorStack.isEmpty() || "(".equals(s)) {   //如果操作符栈为空，或者为"("，则入栈
                    operatorStack.push(s);
                } else if (priority(s) > priority((String) operatorStack.peek())) {  //如果当前操作符优先级大于栈顶操作符优先级，则入栈
                    operatorStack.push(s);
                } else if (")".equals(s)) {  //如果是右括号")"，则需要依次弹出操作符栈的操作符以及操作数栈的两个操作数，直到弹出左括号"("
                    String op = operatorStack.pop();
                    while (!"(".equals(op)) {
                        double num1 = numberStack.pop();
                        double num2 = numberStack.pop();
                        numberStack.push(calculate(num1, num2, op));
                        op = operatorStack.pop();
                    }
                } else {   //否则，弹出操作符栈的栈顶操作符，以及弹出操作数栈的两个操作数进行计算，并将计算结果圧入操作数栈，
                    String op = operatorStack.pop();  //弹出栈顶操作符
                    double num1 = numberStack.pop();
                    double num2 = numberStack.pop();
                    numberStack.push(calculate(num1, num2, op));
                    //将当前操作符入栈
                    operatorStack.push(s);
                }
            }
        }

        //当遍历完表达式后，查看操作符栈是否为空，不为空则一次弹出进行运算
        while (!operatorStack.isEmpty()) {
            double num1 = numberStack.pop();
            double num2 = numberStack.pop();
            numberStack.push(calculate(num1, num2, operatorStack.pop()));
        }

        return numberStack.pop();  //此时操作数栈的操作数即为运算结果
    }

    //检查表达式的合法性
    private void checkExpression(String expression) {
        if (expression == null) {
            throw new NullPointerException("表达式不能为空");
        }
        //应该还有更多检查
    }

    //切分表达式，将操作符和数字分别按原来顺序存放在list中
    private List<String> splitExpression(String exp) {
        String expression = exp.replace(" ", "");
        int index = 0;
        String num = "";
        List<String> list = new ArrayList<>();

        while (index != expression.length()){
            String c = expression.substring(index, index + 1);
            //如果是操作数,拼接多位操作数
            if (isNumberOrPoint(c)) {
                num += c;
                //如果下一个字符是操作符，则将当前的num加入到list中
                if (index==expression.length()-1 || isOperator(expression.substring(index + 1, index + 2))) {
                    list.add(num);
                    num = "";
                }
            }
            //否则为操作符，直接加入到list中
            if (isOperator(c)) {
                list.add(c);
            }

            index++;
        }
        return list;
    }

    //判断是否为操作符
    private boolean isOperator(String operator) {
        //正则匹配* / - + ( )符号
        return operator.matches("[\\*\\/\\+\\-\\(\\)]");
    }

    //判断是否为操作数或小数点
    private boolean isNumberOrPoint(String operator) {
        //正则匹配数字或"."符号
        return operator.matches("(\\d+)|(\\.)|(\\d+.\\d+)");
    }

    //返回操作符的优先级
    private int priority(String operator) {
        switch (operator) {
            case "+":
            case "-":
                return 0;
            case "*":
            case "/":
                return 1;
            default:
                return -1;
        }
    }

    //计算
    private double calculate(double num1, double num2, String op) {
        switch (op) {
            case "+":
                return num2 + num1;
            case "-":
                return num2 - num1;
            case "*":
                return num2 * num1;
            case "/":
                return num2 / num1;
            default:
                throw new RuntimeException("暂不支持该操作符[" + op + "]运算");
        }
    }

}
